package org.liaohailong.library.image.victor;

import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import pl.droidsonroids.gif.GifDrawable;

/**
 * Author: liaohailong
 * Date: 2019/7/14
 * Time: 11:09
 * Description: 任务管理
 **/
final class AvatarManager {

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    /**
     * 任务请求队列
     * key = url
     */
    private Map<String, List<AvatarWorker>> submitQueen = new HashMap<>();

    /**
     * 正在执行的任务组
     * key = tag
     */
    private Map<String, List<Task>> workQueen = new HashMap<>();

    /**
     * 需要暂停的任务组
     */
    private List<String> pauseTag = new LinkedList<>();

    /**
     * 调用全局暂停之后堆积的任务
     */
    private Map<String, List<RequestRecorder>> waitQueen = new HashMap<>();

    /**
     * 主线程回调
     */
    private Handler mainHandler = new Handler(Looper.getMainLooper());

    private static final class SingletonHolder {
        private static final AvatarManager INSTANCE = new AvatarManager();
    }

    public static AvatarManager getInstance() {
        return SingletonHolder.INSTANCE;
    }

    @MainThread
    void execute(@NonNull String tag, @NonNull String url,
                 @Nullable ImageView imageView, int width, int height,
                 @Nullable Drawable placeHolder,
                 @Nullable Drawable error,
                 @Nullable Avatar.OnImageCallback callback) {
        // 设置挂载图
        if (imageView != null) {
            imageView.setImageDrawable(placeHolder);
        }

        // 检查是否为暂停状态的tag
        if (pauseTag.contains(tag)) {
            List<RequestRecorder> waitList = getWaitList(tag);
            waitList.add(new RequestRecorder(tag, url, imageView, width, height, placeHolder, error, callback));
            return;
        }

        AvatarWorker worker = new AvatarWorker(this, url, imageView, width, height, error, callback);

        // 防止重复任务提交
        List<AvatarWorker> submitList = getSubmitList(url);
        if (submitList.size() >= 1) {
            submitList.add(worker);
            return;
        }

        // 提交任务
        submitList.add(worker);
        Future<?> submit = THREAD_POOL_EXECUTOR.submit(worker);

        // 添加任务编组
        List<Task> taskList = workQueen.get(tag);
        if (taskList == null) {
            taskList = new LinkedList<>();
            workQueen.put(tag, taskList);
        }
        taskList.add(new Task(imageView, submit));
    }

    private List<AvatarWorker> getSubmitList(String url) {
        List<AvatarWorker> submitList = submitQueen.get(url);
        if (submitList == null) {
            submitList = new LinkedList<>();
            submitQueen.put(url, submitList);
        }
        return submitList;
    }

    private List<RequestRecorder> getWaitList(String tag) {
        List<RequestRecorder> requestRecorders = waitQueen.get(tag);
        if (requestRecorders == null) {
            requestRecorders = new LinkedList<>();
            waitQueen.put(tag, requestRecorders);
        }
        return requestRecorders;
    }


    void postMainThread(Runnable runnable) {
        mainHandler.post(runnable);
    }

    /**
     * 移除并且检查已请求的任务
     * 如果有若干请求都为同一个url，那么只有第一个url会进行网络请求，
     * 当请求结束后，无论成功或失败，都会走这个方法
     * 此处需要根据结果，灵活处理其他相同的请求任务
     *
     * @param success 请求是否成功
     * @param worker  请求体
     */
    void removeAndCheckSubmitQueen(boolean success, AvatarWorker worker) {
        String url = worker.getUrl();
        List<AvatarWorker> submitList = getSubmitList(url);

        if (success) {
            if (submitList.isEmpty()) {
                return;
            }
            List<AvatarWorker> record = new LinkedList<>(submitList);
            submitList.remove(worker);
            for (AvatarWorker avatarWorker : record) {
                THREAD_POOL_EXECUTOR.execute(avatarWorker);
            }
        } else {
            submitList.remove(worker);
            if (submitList.isEmpty()) {
                return;
            }
            AvatarWorker remove = submitList.remove(0);
            THREAD_POOL_EXECUTOR.execute(remove);
        }
    }

    /**
     * 以编队的方式，AOP停止任务
     *
     * @param tag 编队标记
     */
    void stopRequests(String tag) {
        if (pauseTag.contains(tag)) {
            return;
        }
        pauseTag.add(tag);
    }

    /**
     * 以编队的方式，AOP恢复任务
     * 恢复暂停后堆积的任务
     *
     * @param tag 编队标记
     */
    void resumeRequests(String tag) {
        pauseTag.remove(tag);
        List<RequestRecorder> waitList = getWaitList(tag);
        LinkedList<RequestRecorder> copy = new LinkedList<>(waitList);
        waitList.clear();

        RequestRecorder poll = copy.pollFirst();
        while (poll != null) {
            execute(poll.tag,
                    poll.url,
                    poll.imageView.get(),
                    poll.width,
                    poll.height,
                    poll.placeHolder.get(),
                    poll.error.get(),
                    poll.callback);
            poll = copy.pollFirst();
        }
    }

    /**
     * 以编队的方式，暂停gif
     * 暂停后会使新进的任务堆积在waitQueen中
     *
     * @param tag 编队标记
     */
    void pauseGif(String tag) {
        List<Task> tasks = workQueen.get(tag);
        if (tasks != null) {
            for (Task task : tasks) {
                ImageView imageView = task.imageView.get();
                if (imageView != null) {
                    Drawable drawable = imageView.getDrawable();
                    if (drawable instanceof GifDrawable) {
                        GifDrawable gifDrawable = (GifDrawable) drawable;
                        if (gifDrawable.isRunning()) {
                            gifDrawable.stop();
                        }
                    }
                }
            }
        }
    }

    /**
     * 以编队的方式，恢复gif
     *
     * @param tag 编队标记
     */
    void resumeGif(String tag) {
        List<Task> tasks = workQueen.get(tag);
        if (tasks != null) {
            for (Task task : tasks) {
                ImageView imageView = task.imageView.get();
                if (imageView != null) {
                    Drawable drawable = imageView.getDrawable();
                    if (drawable instanceof GifDrawable) {
                        GifDrawable gifDrawable = (GifDrawable) drawable;
                        if (!gifDrawable.isRunning()) {
                            gifDrawable.start();
                        }
                    }
                }
            }
        }
    }

    /**
     * 以编队的方式，移除任务
     *
     * @param tag         编队标记
     * @param stopLoading 是否停止请求
     */
    public void clear(String tag, boolean stopLoading) {
        List<Task> taskList = workQueen.get(tag);
        if (taskList != null) {
            if (stopLoading) {
                for (Task task : taskList) {
                    if (task.submit == null) {
                        continue;
                    }
                    if (task.submit.isDone()) {
                        continue;
                    }
                    if (task.submit.isCancelled()) {
                        continue;
                    }
                    task.submit.cancel(false);
                }
            }
            taskList.clear();
        }

        List<RequestRecorder> recorders = waitQueen.get(tag);
        if (recorders != null) {
            recorders.clear();
        }
    }


    private final class Task {
        private WeakReference<ImageView> imageView;
        private Future<?> submit;

        Task(ImageView imageView, Future<?> submit) {
            this.imageView = new WeakReference<>(imageView);
            this.submit = submit;
        }
    }

    private final class RequestRecorder {
        private String tag;
        private String url;
        private WeakReference<ImageView> imageView;
        private int width;
        private int height;
        private WeakReference<Drawable> placeHolder;
        private WeakReference<Drawable> error;
        private Avatar.OnImageCallback callback;

        RequestRecorder(
                String tag,
                String url,
                ImageView imageView,
                int width,
                int height,
                Drawable placeHolder,
                Drawable error,
                Avatar.OnImageCallback callback) {
            this.tag = tag;
            this.url = url;
            this.imageView = new WeakReference<>(imageView);
            this.width = width;
            this.height = height;
            this.placeHolder = new WeakReference<>(placeHolder);
            this.error = new WeakReference<>(error);
            this.callback = callback;
        }
    }
}
